#include "stdafx.h"
#include "mp4.h"
#include <WinSock2.h>

inline int32_t read_char(uint8_t const* buffer)
{
	return buffer[0];
}
inline uint32_t read_int32(void const* buffer)
{
	uint8_t* p = (uint8_t*)buffer;
	return htonl(*(uint32_t*)p);//(p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3];
}
inline void write_int32(void* outbuffer, uint32_t value)
{
	uint8_t* p = (uint8_t*)outbuffer;
	p[0] = (uint8_t)((value >> 24) & 0xff);
	p[1] = (uint8_t)((value >> 16) & 0xff);
	p[2] = (uint8_t)((value >> 8) & 0xff);
	p[3] = (uint8_t)((value >> 0) & 0xff);
}
inline uint32_t atom_header_size(uint8_t* atom_bytes)
{
	return (atom_bytes[0] << 24) +
		(atom_bytes[1] << 16) +
		(atom_bytes[2] << 8) +
		(atom_bytes[3]);
}
inline uint8_t* atom_read_header(uint8_t* buffer, struct atom_t* atom)
{
#if __WORDSIZE == 64
	atom->start_ = (uint64_t)buffer;
#else
	atom->start_ = (uint32_t)buffer;
#endif
	memcpy(&atom->type_, &buffer[4], 4);
	atom->size_ = atom_header_size(buffer);
	atom->end_ = atom->start_ + atom->size_;

	return buffer + ATOM_PREAMBLE_SIZE;
}
inline uint8_t* atom_skip(struct atom_t const* atom)
{
#if __WORDSIZE == 64
	uint64_t p = atom->end_;
#else
	uint32_t p = atom->end_;
#endif
	return (uint8_t*)p;
}
inline int32_t atom_is(struct atom_t const* atom, const char* type)
{
	return (atom->type_[0] == type[0] &&
		atom->type_[1] == type[1] &&
		atom->type_[2] == type[2] &&
		atom->type_[3] == type[3]);
}
inline uint32_t stts_get_entries(uint8_t const* stts)
{
	return read_int32(stts + 4);
}
inline void stts_get_sample_count_and_duration(uint8_t const* stts, uint32_t idx, uint32_t* sample_count, uint32_t* sample_duration)
{
	uint8_t const* table = stts + 8 + idx * 8;
	*sample_count = read_int32(table);
	*sample_duration = read_int32(table + 4);
}
inline uint32_t ctts_get_entries(uint8_t const* ctts)
{
	return read_int32(ctts + 4);
}
inline void ctts_get_sample_count_and_offset(uint8_t const* ctts, uint32_t idx, uint32_t* sample_count, uint32_t* sample_offset)
{
	uint8_t const* table = ctts + 8 + idx * 8;
	*sample_count = read_int32(table);
	*sample_offset = read_int32(table + 4);
}
uint32_t ctts_get_samples(uint8_t const* ctts)
{
	int64_t samples = 0;
	int64_t entries = ctts_get_entries(ctts);
	int32_t i;
	for(i = 0; i != entries; ++i)
	{
		uint32_t sample_count;
		uint32_t sample_offset;
		ctts_get_sample_count_and_offset(ctts, i, &sample_count, &sample_offset);
		samples += sample_count;
	}

	return samples;
}
inline uint32_t stsc_get_entries(uint8_t const* stsc)
{
	return read_int32(stsc + 4);
}
inline void stsc_get_table(uint8_t const* stsc, uint32_t i, struct stsc_table_t *stsc_table)
{
	struct stsc_table_t* table = (struct stsc_table_t*)(stsc + 8);
	stsc_table->chunk_ = read_int32(&table[i].chunk_) - 1;
	stsc_table->samples_ = read_int32(&table[i].samples_);
	stsc_table->id_ = read_int32(&table[i].id_);
}
uint32_t stsc_get_chunk(uint8_t* stsc, uint32_t sample)
{
	uint32_t entries = read_int32(stsc + 4);
	struct stsc_table_t* table = (struct stsc_table_t*)(stsc + 8);


	if(entries == 0)
	{
		return 0;
	}
	else
	{
		uint32_t total = 0;
		uint32_t chunk1 = 1;
		uint32_t chunk1samples = 0;
		uint32_t chunk2entry = 0;
		uint32_t chunk, chunk_sample;

		do
		{
			uint32_t range_samples;
			uint32_t chunk2 = read_int32(&table[chunk2entry].chunk_);
			chunk = chunk2 - chunk1;
			range_samples = chunk * chunk1samples;

			if(sample < total + range_samples)
			{
				break;
			}

			chunk1samples = read_int32(&table[chunk2entry].samples_);
			chunk1 = chunk2;

			if(chunk2entry < entries)
			{
				chunk2entry++;
				total += range_samples;
			}
		} while(chunk2entry < entries);

		if(chunk1samples)
		{
			uint32_t sample_in_chunk = (sample - total) % chunk1samples;
			if(sample_in_chunk != 0)
			{
				//printf("ERROR: sample must be chunk aligned: %d\n", sample_in_chunk);
			}
			chunk = (sample - total) / chunk1samples + chunk1;
		}
		else
		{
			chunk = 1;
		}

		chunk_sample = total + (chunk - chunk1) * chunk1samples;

		return chunk;
	}
}
uint32_t stsc_get_samples(uint8_t* stsc)
{
	uint32_t entries = read_int32(stsc + 4);
	struct stsc_table_t* table = (struct stsc_table_t*)(stsc + 8);
	uint32_t samples = 0;
	uint32_t i;
	for(i = 0; i != entries; ++i)
	{
		samples += read_int32(&table[i].samples_);
	}
	return samples;
}
inline uint32_t stco_get_entries(uint8_t const* stco)
{
	return read_int32(stco + 4);
}

inline uint32_t stco_get_offset(uint8_t const* stco, int32_t idx)
{
	uint32_t const* table = (uint32_t const*)(stco + 8);
	return read_int32(&table[idx]);
}

uint32_t stsz_get_sample_size(uint8_t const* stsz)
{
	return read_int32(stsz + 4);
}

uint32_t stsz_get_entries(uint8_t const* stsz)
{
	return read_int32(stsz + 8);
}

inline uint32_t stsz_get_size(uint8_t const* stsz, uint32_t idx)
{
	uint32_t const* table = (uint32_t const*)(stsz + 12);
	return read_int32(&table[idx]);
}

uint32_t stts_get_duration(uint8_t const* stts)
{
	int64_t duration = 0;
	int64_t entries = stts_get_entries(stts);
	int32_t i;
	for(i = 0; i != entries; ++i)
	{
		uint32_t sample_count;
		uint32_t sample_duration;
		stts_get_sample_count_and_duration(stts, i, &sample_count, &sample_duration);
		duration += sample_duration * sample_count;
	}

	return duration;
}

uint32_t stts_get_samples(uint8_t const* stts)
{
	int64_t samples = 0;
	int64_t entries = stts_get_entries(stts);
	int32_t i;
	for(i = 0; i != entries; ++i)
	{
		uint32_t sample_count;
		uint32_t sample_duration;
		stts_get_sample_count_and_duration(stts, i, &sample_count, &sample_duration);
		samples += sample_count;
	}

	return samples;
}

uint32_t stts_get_sample(uint8_t const* stts, uint32_t time)
{
	uint32_t stts_index = 0;
	uint32_t stts_count;

	uint32_t ret = 0;
	uint32_t time_count = 0;

	uint32_t entries = stts_get_entries(stts);
	for(; stts_index != entries; ++stts_index)
	{
		uint32_t sample_count;
		uint32_t sample_duration;
		stts_get_sample_count_and_duration(stts, stts_index, &sample_count, &sample_duration);
		if(time_count + sample_duration * sample_count >= time)
		{
			stts_count = (time - time_count) / sample_duration;
			time_count += stts_count * sample_duration;
			ret += stts_count;
			break;
		}
		else
		{
			time_count += sample_duration * sample_count;
			ret += sample_count;
		}
	}
	return ret;
}

uint32_t stts_get_time(uint8_t const* stts, uint32_t sample)
{
	uint32_t ret = 0;
	uint32_t stts_index = 0;
	uint32_t sample_count = 0;

	for(;;)
	{
		uint32_t table_sample_count;
		uint32_t table_sample_duration;
		stts_get_sample_count_and_duration(stts, stts_index, &table_sample_count, &table_sample_duration);

		if(sample_count + table_sample_count > sample)
		{
			uint32_t stts_count = (sample - sample_count);
			ret += stts_count * table_sample_duration;
			break;
		}
		else
		{
			sample_count += table_sample_count;
			ret += table_sample_count * table_sample_duration;
			stts_index++;
		}
	}
	return ret;
}


void stbl_parse(struct stbl_t* stbl, uint8_t* buffer, uint32_t size)
{
	struct atom_t leaf_atom;
	uint8_t* buffer_start = buffer;
	stbl->stss_ = 0;
	stbl->ctts_ = 0;

	stbl->start_ = buffer;

	while(buffer < buffer_start + size)
	{
		buffer = atom_read_header(buffer, &leaf_atom);

		//atom_print(&leaf_atom);

		if(atom_is(&leaf_atom, "stts"))
		{
			stbl->stts_ = buffer;
		}
		else if(atom_is(&leaf_atom, "stss"))
		{
			stbl->stss_ = buffer;
		}
		else if(atom_is(&leaf_atom, "stsc"))
		{
			stbl->stsc_ = buffer;
		}
		else if(atom_is(&leaf_atom, "stsz"))
		{
			stbl->stsz_ = buffer;
		}
		else if(atom_is(&leaf_atom, "stco"))
		{
			stbl->stco_ = buffer;
		}
		else if(atom_is(&leaf_atom, "co64"))
		{
			perror("TODO: co64");
		}
		else if(atom_is(&leaf_atom, "ctts"))
		{
			stbl->ctts_ = buffer;
		}


		buffer = atom_skip(&leaf_atom);
	}
}

void minf_parse(struct minf_t* minf, uint8_t* buffer, uint32_t size)
{
	struct atom_t leaf_atom;
	uint8_t* buffer_start = buffer;

	minf->start_ = buffer;

	while(buffer < buffer_start + size)
	{
		buffer = atom_read_header(buffer, &leaf_atom);

		//atom_print(&leaf_atom);

		if(atom_is(&leaf_atom, "stbl"))
		{
			stbl_parse(&minf->stbl_, buffer, leaf_atom.size_ - ATOM_PREAMBLE_SIZE);
		}

		buffer = atom_skip(&leaf_atom);
	}
}
void mdia_parse(struct mdia_t* mdia, uint8_t* buffer, uint32_t size)
{
	struct atom_t leaf_atom;
	uint8_t* buffer_start = buffer;

	mdia->start_ = buffer;

	while(buffer < buffer_start + size)
	{
		buffer = atom_read_header(buffer, &leaf_atom);

		//atom_print(&leaf_atom);

		if(atom_is(&leaf_atom, "mdhd"))
		{
			mdia->mdhd_ = buffer;
		}
		else if(atom_is(&leaf_atom, "minf"))
		{
			minf_parse(&mdia->minf_, buffer, leaf_atom.size_ - ATOM_PREAMBLE_SIZE);
		}

		buffer = atom_skip(&leaf_atom);
	}
}

inline  void trak_init(struct trak_t* trak)
{
	trak->chunks_ = 0;
	trak->samples_ = 0;
}

inline  void trak_exit(struct trak_t* trak)
{
	if(trak->chunks_)
	{
		delete [] trak->chunks_;
	}

	if(trak->samples_)
	{
		delete [] trak->samples_;
	}
}

void trak_parse(struct trak_t* trak, uint8_t* buffer, uint32_t size)
{
	struct atom_t leaf_atom;
	uint8_t* buffer_start = buffer;

	trak->start_ = buffer;

	while(buffer < buffer_start + size)
	{
		buffer = atom_read_header(buffer, &leaf_atom);

		//atom_print(&leaf_atom);

		if(atom_is(&leaf_atom, "tkhd"))
		{
			trak->tkhd_ = buffer;
		}
		else if(atom_is(&leaf_atom, "mdia"))
		{
			mdia_parse(&trak->mdia_, buffer, leaf_atom.size_ - ATOM_PREAMBLE_SIZE);
		}

		buffer = atom_skip(&leaf_atom);
	}
}

inline  void moov_init(struct moov_t* moov)
{
	moov->tracks_ = 0;
}

inline void moov_exit(struct moov_t* moov)
{
	uint32_t i;
	for(i = 0; i != moov->tracks_; ++i)
	{
		trak_exit(&moov->traks_[i]);
	}
}

void trak_build_index(struct trak_t* trak)
{
	void const* stco = trak->mdia_.minf_.stbl_.stco_;

	trak->chunks_size_ = stco_get_entries((const uint8_t*)stco);
	int size=trak->chunks_size_*sizeof(chunks_t);
	trak->chunks_= new chunks_t[trak->chunks_size_+5];
	if (trak->chunks_==NULL)
	{
		TRACE("%d",GetLastError());
	}
	{
		uint32_t i;
		for(i = 0; i != trak->chunks_size_; ++i)
		{
			trak->chunks_[i].pos_ = stco_get_offset((const uint8_t*)stco, i);
		}
	}

	// process chunkmap:
	{
		void const* stsc = trak->mdia_.minf_.stbl_.stsc_;
		uint32_t last = trak->chunks_size_;
		uint32_t i = stsc_get_entries((const uint8_t*)stsc);
		while(i > 0)
		{
			struct stsc_table_t stsc_table;
			uint32_t j;

			--i;

			stsc_get_table((const uint8_t*)stsc, i, &stsc_table);
			for(j = stsc_table.chunk_; j < last; j++)
			{
				trak->chunks_[j].id_ = stsc_table.id_;
				trak->chunks_[j].size_ = stsc_table.samples_;
			}
			last = stsc_table.chunk_;
		}
	}

	// calc pts of chunks:
	{
		void const* stsz = trak->mdia_.minf_.stbl_.stsz_;
		uint32_t sample_size = stsz_get_sample_size((const uint8_t*)stsz);
		uint32_t s = 0;
		{
			uint32_t j;
			for(j = 0; j < trak->chunks_size_; j++)
			{
				trak->chunks_[j].sample_ = s;
				s += trak->chunks_[j].size_;
			}
		}

		if(sample_size == 0)
		{
			trak->samples_size_ = stsz_get_entries((const uint8_t*)stsz);
		}
		else
		{
			trak->samples_size_ = s;
		}

		trak->samples_ = new samples_t[trak->samples_size_+5];

		if(sample_size == 0)
		{
			uint32_t i;
			for(i = 0; i != trak->samples_size_ ; ++i)
				trak->samples_[i].size_ = stsz_get_size((const uint8_t*)stsz, i);
		}
		else
		{
			uint32_t i;
			for(i = 0; i != trak->samples_size_ ; ++i)
				trak->samples_[i].size_ = sample_size;
		}
	}

	// calc pts:
	{
		void const* stts = trak->mdia_.minf_.stbl_.stts_;
		unsigned int s = 0;
		unsigned int entries = stts_get_entries((const uint8_t*)stts);
		unsigned int j;
		unsigned int pts = 0;
		for(j = 0; j < entries; j++)
		{
			unsigned int i;
			unsigned int sample_count;
			unsigned int sample_duration;
			stts_get_sample_count_and_duration((const uint8_t*)stts, j,
				&sample_count, &sample_duration);
			for(i = 0; i < sample_count; i++)
			{
				trak->samples_[s].pts_ = pts;
				++s;
				pts += sample_duration;
			}
		} 
	}


	// calc composition times:
	{
		void const* ctts = trak->mdia_.minf_.stbl_.ctts_;
		if(ctts)
		{
			uint32_t s = 0;
			uint32_t entries = ctts_get_entries((const uint8_t*)ctts);
			uint32_t j;
			for(j = 0; j < entries; j++)
			{
				uint32_t i;
				uint32_t sample_count;
				uint32_t sample_offset;
				ctts_get_sample_count_and_offset((const uint8_t*)ctts, j, &sample_count, &sample_offset);
				for(i = 0; i < sample_count; i++)
				{
					trak->samples_[s].cto_ = sample_offset;
					++s;
				}
			}
		}
	}

	// calc sample offsets
	{
		uint32_t s = 0;
		uint32_t j;
		for(j = 0; j < trak->chunks_size_; j++)
		{
			uint32_t pos = trak->chunks_[j].pos_;
			uint32_t i;
			for(i = 0; i < trak->chunks_[j].size_; i++)
			{
				trak->samples_[s].pos_ = pos;
				pos += trak->samples_[s].size_;
				++s;
			}
		}
	}
}

void trak_write_index(struct trak_t* trak, uint32_t start, uint32_t end)
{
	// write samples [start,end>

	{
		uint8_t* stts = trak->mdia_.minf_.stbl_.stts_;
		uint32_t entries = 0;
		struct stts_table_t* table = (struct stts_table_t*)(stts + 8);
		uint32_t s;
		for(s = start; s != end; ++s)
		{
			uint32_t sample_count = 1;
			uint32_t sample_duration =
				trak->samples_[s + 1].pts_ - trak->samples_[s].pts_;
			while(s != end - 1)
			{
				if((trak->samples_[s + 1].pts_ - trak->samples_[s].pts_) != sample_duration)
				{
					break;
				}
				++sample_count;
				++s;
			}
			// write entry
			write_int32(&table[entries].sample_count_, sample_count);
			write_int32(&table[entries].sample_duration_, sample_duration);
			++entries;
		}
		write_int32(stts + 4, entries);
		if(stts_get_samples(stts) != end - start)
		{
			//printf("ERROR: stts_get_samples=%d, should be %d\n", stts_get_samples(stts), end - start);
		}
	}

	{
		uint8_t* ctts = trak->mdia_.minf_.stbl_.ctts_;
		if(ctts)
		{
			uint32_t entries = 0;
			struct ctts_table_t* table = (struct ctts_table_t*)(ctts + 8);
			uint32_t s;
			for(s = start; s != end; ++s)
			{
				uint32_t sample_count = 1;
				uint32_t sample_offset = trak->samples_[s].cto_;
				while(s != end - 1)
				{
					if(trak->samples_[s + 1].cto_ != sample_offset)
					{
						break;
					}
					++sample_count;
					++s;
				}
				// write entry
				write_int32(&table[entries].sample_count_, sample_count);
				write_int32(&table[entries].sample_offset_, sample_offset);
				++entries;
			}
			write_int32(ctts + 4, entries);
			if(ctts_get_samples(ctts) != end - start)
			{
				//printf("ERROR: ctts_get_samples=%d, should be %d\n", ctts_get_samples(ctts), end - start);
			}
		}
	}

	// process chunkmap:
	{
		uint8_t* stsc = trak->mdia_.minf_.stbl_.stsc_;
		stsc_table_t* stsc_table = (struct stsc_table_t*)(stsc + 8);
		uint32_t i;
		for(i = 0; i != trak->chunks_size_; ++i)
		{
			if(trak->chunks_[i].sample_ + trak->chunks_[i].size_ > start)
			{
				break;
			}
		}

		{
			uint32_t stsc_entries = 0;
			uint32_t chunk_start = i;
			uint32_t samples = trak->chunks_[i].sample_ + trak->chunks_[i].size_ - start;
			uint32_t id = trak->chunks_[i].id_;

			// write entry [chunk,samples,id]
			write_int32(&stsc_table[stsc_entries].chunk_, 1);
			write_int32(&stsc_table[stsc_entries].samples_, samples);
			write_int32(&stsc_table[stsc_entries].id_, id);
			++stsc_entries;
			if(i != trak->chunks_size_)
			{
				for(i += 1; i != trak->chunks_size_; ++i)
				{
					if(trak->chunks_[i].size_ != samples)
					{
						samples = trak->chunks_[i].size_;
						id = trak->chunks_[i].id_;
						write_int32(&stsc_table[stsc_entries].chunk_, i - chunk_start + 1);
						write_int32(&stsc_table[stsc_entries].samples_, samples);
						write_int32(&stsc_table[stsc_entries].id_, id);
						++stsc_entries;
					}
				}
			}
			write_int32(stsc + 4, stsc_entries);
			{
				uint8_t* stco = trak->mdia_.minf_.stbl_.stco_;
				uint32_t entries = read_int32(stco + 4);
				uint32_t* stco_table = (uint32_t*)(stco + 8);
				memmove(stco_table, &stco_table[chunk_start], (entries - chunk_start) * sizeof(uint32_t));
				write_int32(stco + 4, entries - chunk_start);

				// patch first chunk with correct sample offset
				write_int32(stco_table, trak->samples_[start].pos_);
			}
		}
	}

	// process sync samples:
	if(trak->mdia_.minf_.stbl_.stss_)
	{
		uint8_t* stss = trak->mdia_.minf_.stbl_.stss_;
		uint32_t entries = read_int32(stss + 4);
		uint32_t* table = (uint32_t*)(stss + 8);
		uint32_t stss_start;
		uint32_t i;
		for(i = 0; i != entries; ++i)
		{
			if(read_int32(&table[i]) >= start + 1)
			{
				break;
			}
		}
		stss_start = i;
		for(; i != entries; ++i)
		{
			uint32_t sync_sample = read_int32(&table[i]);
			if(sync_sample >= end + 1) break;
			write_int32(&table[i - stss_start], sync_sample - start);
		}
		write_int32(stss + 4, i - stss_start);
	}

	// process sample sizes
	{
		uint8_t* stsz = trak->mdia_.minf_.stbl_.stsz_;
		if(stsz_get_sample_size(stsz) == 0)
		{
			uint32_t* table = (uint32_t*)(stsz + 12);
			memmove(table, &table[start], (end - start) * sizeof(uint32_t));
			write_int32(stsz + 8, end - start);
		}
	}
}

int32_t moov_parse(struct moov_t* moov, uint8_t* buffer, uint32_t size)
{
	struct atom_t leaf_atom;
	uint8_t* buffer_start = buffer;


	moov->start_ = buffer;

	while(buffer < buffer_start + size)
	{
		buffer = atom_read_header(buffer, &leaf_atom);

		//atom_print(&leaf_atom);

		if(atom_is(&leaf_atom, "cmov"))
		{
			return 0;
		}
		else if(atom_is(&leaf_atom, "mvhd"))
		{
			moov->mvhd_ = buffer;
		}
		else if(atom_is(&leaf_atom, "trak"))
		{
			if(moov->tracks_ == MAX_TRACKS)
			{
				return 0;
			}
			else
			{
				struct trak_t* trak = &moov->traks_[moov->tracks_];
				trak_init(trak);
				trak_parse(trak, buffer, leaf_atom.size_ - ATOM_PREAMBLE_SIZE);
				++moov->tracks_;
			}
		}
		buffer = atom_skip(&leaf_atom);
	}

	// build the indexing tables
	{
		uint32_t i;
		for(i = 0; i != moov->tracks_; ++i)
		{
			trak_build_index(&moov->traks_[i]);
		}
	}

	return 1;
}

inline  void stco_shift_offsets(uint8_t* stco, int32_t offset)
{
	uint32_t entries = read_int32(stco + 4);
	uint32_t* table = (uint32_t*)(stco + 8);
	uint32_t i;
	for(i = 0; i != entries; ++i) write_int32(&table[i], (read_int32(&table[i]) + offset));
}

inline  void trak_shift_offsets(struct trak_t* trak, int32_t offset)
{
	uint8_t* stco = trak->mdia_.minf_.stbl_.stco_;
	stco_shift_offsets(stco, offset);
}

inline  void moov_shift_offsets(struct moov_t* moov, int32_t offset)
{
	uint32_t i;
	for(i = 0; i != moov->tracks_; ++i)
	{
		trak_shift_offsets(&moov->traks_[i], offset);
	}
}

inline  int64_t mvhd_get_time_scale(uint8_t* mvhd)
{
	int32_t version = read_char(mvhd);
	uint8_t* p = mvhd + (version == 0 ? 12 : 20);
	return read_int32(p);
}

inline  void mvhd_set_duration(uint8_t* mvhd, int64_t duration)
{
	int32_t version = read_char(mvhd);
	if(version == 0)
	{
		write_int32(mvhd + 16, duration);
	}
	else
	{
		perror("mvhd_set_duration");
	}
}

inline  int64_t mdhd_get_time_scale(uint8_t* mdhd)
{
	return read_int32(mdhd + 12);
}

inline  void mdhd_set_duration(uint8_t* mdhd, uint32_t duration)
{
	write_int32(mdhd + 16, duration);
}

inline  void tkhd_set_duration(uint8_t* tkhd, uint32_t duration)
{
	int32_t version = read_char(tkhd);
	if(version == 0)
	{
		write_int32(tkhd + 20, duration);
	}
	else
	{
		perror("tkhd_set_duration");
	}
}

inline  uint32_t stss_get_entries(uint8_t const* stss)
{
	return read_int32(stss + 4);
}

inline  uint32_t stss_get_sample(uint8_t const* stss, uint32_t idx)
{
	uint8_t const* p = stss + 8 + idx * 4;
	return read_int32(p);
}

uint32_t stss_get_nearest_keyframe(uint8_t const* stss, uint32_t sample)
{
	// scan the sync samples to find the key frame that precedes the sample number
	uint32_t i;
	uint32_t entries = stss_get_entries(stss);
	uint32_t table_sample = 0;
	for(i = 0; i != entries; ++i)
	{
		table_sample = stss_get_sample(stss, i);
		if(table_sample >= sample) break;
	}
	if(table_sample == sample)
	{
		return table_sample;
	}
	else
	{
		return stss_get_sample(stss, i - 1);
	}
}

inline  uint32_t stbl_get_nearest_keyframe(struct stbl_t const* stbl, uint32_t sample)
{
	// If the sync atom is not present, all samples are implicit sync samples.
	if(!stbl->stss_) return sample;

	return stss_get_nearest_keyframe(stbl->stss_, sample);
}

uint32_t moov_seek(uint8_t* moov_data, uint32_t size, double start_time, uint64_t* mdat_start, uint32_t* mdat_size, uint32_t offset)
{
	moov_t* moov = new moov_t;
	moov_init(moov);
	if(!moov_parse(moov, moov_data, size))
	{
		moov_exit(moov);
		delete moov;
		return 0;
	}

	{
		int64_t moov_time_scale = mvhd_get_time_scale(moov->mvhd_);
		uint32_t start = (uint32_t)(start_time * moov_time_scale);
		uint32_t bytes_to_skip = UINT_MAX;
		uint32_t i;

		// for every trak, convert seconds to sample (time-to-sample).
		// adjust sample to keyframe
		uint32_t trak_sample_start[MAX_TRACKS];

		uint32_t moov_duration = 0;

		// clayton.mp4 has a third track with one sample that lasts the whole clip.
		// Assuming the first two tracks are the audio and video track, we patch
		// the remaining tracks to 'free' atoms.
		if(moov->tracks_ > 1)
		{
			for(i = 2; i != moov->tracks_; ++i)
			{
				// patch 'trak' to 'free'
				uint8_t* p = moov->traks_[i].start_ - 4;
				p[0] = 'f';
				p[1] = 'r';
				p[2] = 'e';
				p[3] = 'e';
			}
			moov->tracks_ = 2;
		}

		for(i = 0; i != moov->tracks_; ++i)
		{
			struct trak_t* trak = &moov->traks_[i];
			struct stbl_t* stbl = &trak->mdia_.minf_.stbl_;
			int64_t trak_time_scale = mdhd_get_time_scale(trak->mdia_.mdhd_);
			double moov_to_trak_time = (double)trak_time_scale / (double)moov_time_scale;
			double trak_to_moov_time = (double)moov_time_scale / (double)trak_time_scale;

			start = stts_get_sample(stbl->stts_, (uint32_t)(start * moov_to_trak_time));
			start = stbl_get_nearest_keyframe(stbl, start + 1) - 1;
			trak_sample_start[i] = start;
			start = (uint32_t)(stts_get_time(stbl->stts_, start) * trak_to_moov_time);
		}


		//printf("start=%u\n", start);

		for(i = 0; i != moov->tracks_; ++i)
		{
			struct trak_t* trak = &moov->traks_[i];
			struct stbl_t* stbl = &trak->mdia_.minf_.stbl_;

			uint32_t start_sample = trak_sample_start[i];
			uint32_t end_sample = trak->samples_size_;

			trak_write_index(trak, start_sample, end_sample);

			{
				unsigned skip = trak->samples_[start_sample].pos_ - trak->samples_[0].pos_;
				if(skip < bytes_to_skip) bytes_to_skip = skip;
				//printf("Trak can skip %u bytes\n", skip);
			}

			{
				// fixup trak (duration)
				uint32_t trak_duration = stts_get_duration(stbl->stts_);
				int64_t trak_time_scale = mdhd_get_time_scale(trak->mdia_.mdhd_);
				double trak_to_moov_time = (double)moov_time_scale / (double)trak_time_scale;
				uint32_t duration = (int64_t)((double)trak_duration * trak_to_moov_time);
				mdhd_set_duration(trak->mdia_.mdhd_, trak_duration);
				tkhd_set_duration(trak->tkhd_, duration);
				//printf("trak: new_duration=%d\n", duration);

				if(duration > moov_duration) moov_duration = duration;
			}

			//printf("stco.size=%d, ", read_int32(stbl->stco_ + 4));
			//printf("stts.size=%d samples=%d\n", read_int32(stbl->stts_ + 4), stts_get_samples(stbl->stts_));
			//printf("stsz.size=%d\n", read_int32(stbl->stsz_ + 8));
			//printf("stsc.samples=%d\n", stsc_get_samples(stbl->stsc_));
		}
		mvhd_set_duration(moov->mvhd_, moov_duration);

		offset -= bytes_to_skip;

		//printf("shifting offsets by %d\n", offset);
		moov_shift_offsets(moov, offset);

		*mdat_start += bytes_to_skip;
		*mdat_size -= bytes_to_skip;
	}

	moov_exit(moov);
	delete moov;

	return 1;
}
inline  void write_char(uint8_t* outbuffer, int32_t value)
{
	outbuffer[0] = (uint8_t)(value);
}
int32_t atom_read_header_file(CFile* infile, struct atom_t* atom,int nStart)
{
	uint8_t atom_bytes[ATOM_PREAMBLE_SIZE];

	atom->start_ = nStart;
	infile->Read(atom_bytes,ATOM_PREAMBLE_SIZE);
	memcpy(&atom->type_[0], &atom_bytes[4], 4);
	atom->size_ = atom_header_size(atom_bytes);
	atom->end_ = atom->start_ + atom->size_;

	return 1;
}
void atom_write_header(uint8_t* outbuffer, struct atom_t* atom)
{
	int32_t i;
	write_int32(outbuffer, atom->size_);
	for(i = 0; i != 4; ++i) write_char(outbuffer + 4 + i, atom->type_[i]);
}
int atom_skip_file(CFile* infile, struct atom_t const* atom)
{
	infile->Seek(atom->end_,0);
	return atom->end_;
}


